package com.smart.manager.system.handler;


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.smart.manager.system.entities.models.SystemOperationLog;
import com.smart.utils.AclUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Objects;

/**
 * 操作日志aop
 *
 * @author 夏绪秋
 */
@Aspect
@Component
@Log4j2
public class OperationLogAop {

    @Pointcut("@annotation( cn.dev33.satoken.annotation.SaCheckPermission )")
    public void declarePointCut() {}

    @After("declarePointCut()")

    public void saveLog(JoinPoint joinPoint) {
        SystemOperationLog systemOperationLog = operationLog(joinPoint);
        if (ObjectUtil.isNotEmpty(systemOperationLog)) {
            systemOperationLog.save();
        }
    }


    //==================== 数据封装工具方法==================================


    private static final String[] TYPES = {"java.lang.Integer", "java.lang.Double", "java.lang.Float", "java.lang.Long", "java.lang.Short", "java.lang.ByteFile", "java.lang.Boolean", "java.lang.Char", "java.lang.String", "int", "double", "long", "short", "byte", "boolean", "char", "float"};


    public SystemOperationLog operationLog(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        if (ObjectUtil.isEmpty(request)) {
            return null;
        }


        //不记录查询日志
        if (RequestMethod.GET.name().equalsIgnoreCase(request.getMethod())) {
            return null;
        }
        Class declaringType = joinPoint.getSignature().getDeclaringType();
        String userAgentStr = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
        SystemOperationLog operationLog = new SystemOperationLog();
        //获得解析的基本数据
        operationLog.setActionTime(LocalDateTime.now());
        operationLog.setPackageName(declaringType.getPackage().getName());
        operationLog.setClassName(declaringType.getSimpleName());
        operationLog.setRequestUrl(request.getRequestURL().toString());
        operationLog.setRequestMethod(request.getMethod());
        String clientIp = ServletUtil.getClientIP(request);
        String ip = "0:0:0:0:0:0:0:1".equals(clientIp) ? "127.0.0.1" : clientIp;
        operationLog.setIp(ip);
        operationLog.setBrowser(userAgent.getBrowser().getName() + " " + userAgent.getBrowser().getVersion(userAgentStr));
        operationLog.setOs(userAgent.getOs().getName());
        operationLog.setRequestParam(this.getParams(joinPoint));
        MethodSignature signature = (MethodSignature)
                joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获得操作方法的名称
        ApiOperation apiAnnotation = method.getAnnotation(ApiOperation.class);
        if (ObjectUtil.isNotEmpty(apiAnnotation)) {
            operationLog.setEvent(apiAnnotation.value());
        }
        // 注入操作员姓名
        operationLog.setOperator(AclUtils.getUserName());
        return operationLog;
    }


    private String getParams(JoinPoint point) {
        String className = point.getSignature().getDeclaringType().getSimpleName();
        String methodName = point.getSignature().getName();
        String[] parameterNames = ((MethodSignature) point.getSignature()).getParameterNames();
        StringBuilder sb = null;
        if (Objects.nonNull(parameterNames)) {
            sb = new StringBuilder();
            for (int i = 0; i < parameterNames.length; i++) {
                // 对参数解析(参数有可能为基础数据类型，也可能为一个对象，若为对象则需要解析对象中变量名以及值)
                String value = "";
                if (point.getArgs()[i] == null) {
                    value = "null";
                } else {
                    // 获取对象类型
                    String typeName = point.getArgs()[i].getClass().getTypeName();
                    boolean flag = false;
                    for (String t : TYPES) {
                        //1 判断是否是基础类型
                        if (t.equals(typeName)) {
                            value = point.getArgs()[i].toString();
                            flag = true;
                        }
                        if (flag) {
                            break;
                        }
                    }
                    if (!flag) {

                        value = getFieldsValue(point.getArgs()[i]);
                    }
                }
                sb.append(parameterNames[i]).append(":").append(value).append("; ");
            }
        }
        sb = sb == null ? new StringBuilder() : sb;
        return String.format("\n 请求类名: %s \n 请求方法: %s  \n 请求参数: %s", className, methodName, sb);
    }

    /**
     * 解析实体类，获取实体类中的属性
     */
    public static String getFieldsValue(Object obj) {

        Field[] fields = obj.getClass().getDeclaredFields();
        String typeName = obj.getClass().getTypeName();
        for (String t : TYPES) {
            if (t.equals(typeName)) {
                return "";
            }
        }
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        for (Field f : fields) {
            //在反射时能访问私有变量
            f.setAccessible(true);
            try {
                for (String str : TYPES) {
                    //这边会有问题，如果实体类里面继续包含实体类，这边就没法获取。
                    //其实，我们可以通递归的方式去处理实体类包含实体类的问题。
                    if (f.getType().getName().equals(str)) {
                        sb.append(f.getName()).append(" : ").append(f.get(obj)).append(", ");
                    }
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        sb.append("}");
        return sb.toString();
    }


}
